// synch.cc 
//	Routines for synchronizing threads.  Three kinds of
//	synchronization routines are defined here: semaphores, locks 
//   	and condition variables (the implementation of the last two
//	are left to the reader).
//
// Any implementation of a synchronization routine needs some
// primitive atomic operation.  We assume Nachos is running on
// a uniprocessor, and thus atomicity can be provided by
// turning off interrupts.  While interrupts are disabled, no
// context switch can occur, and thus the current thread is guaranteed
// to hold the CPU throughout, until interrupts are reenabled.
//
// Because some of these routines might be called with interrupts
// already disabled (Semaphore::V for one), instead of turning
// on interrupts at the end of the atomic operation, we always simply
// re-set the interrupt state back to its original value (whether
// that be disabled or enabled).
//
// Copyright (c) 1992-1993 The Regents of the University of California.
// All rights reserved.  See copyright.h for copyright notice and limitation 
// of liability and disclaimer of warranty provisions.

#include "copyright.h"
#include "synch.h"
#include "system.h"

//----------------------------------------------------------------------
// Semaphore::Semaphore
// 	Initialize a semaphore, so that it can be used for synchronization.
//
//	"debugName" is an arbitrary name, useful for debugging.
//	"initialValue" is the initial value of the semaphore.
//----------------------------------------------------------------------

Semaphore::Semaphore(char* debugName, int initialValue)
{
    name = debugName;
    value = initialValue;
    queue = new List;
}

//----------------------------------------------------------------------
// Semaphore::Semaphore
// 	De-allocate semaphore, when no longer needed.  Assume no one
//	is still waiting on the semaphore!
//----------------------------------------------------------------------

Semaphore::~Semaphore()
{
    delete queue;
}

//----------------------------------------------------------------------
// Semaphore::P
// 	Wait until semaphore value > 0, then decrement.  Checking the
//	value and decrementing must be done atomically, so we
//	need to disable interrupts before checking the value.
//
//	Note that Thread::Sleep assumes that interrupts are disabled
//	when it is called.
//----------------------------------------------------------------------

void
Semaphore::P()
{
    IntStatus oldLevel = interrupt->SetLevel(IntOff);	// disable interrupts
    
    while (value == 0) { 			// semaphore not available
	queue->Append((void *)currentThread);	// so go to sleep
	currentThread->Sleep();
    } 
    value--; 					// semaphore available, 
						// consume its value
    
    (void) interrupt->SetLevel(oldLevel);	// re-enable interrupts
}

//----------------------------------------------------------------------
// Semaphore::V
// 	Increment semaphore value, waking up a waiter if necessary.
//	As with P(), this operation must be atomic, so we need to disable
//	interrupts.  Scheduler::ReadyToRun() assumes that threads
//	are disabled when it is called.
//----------------------------------------------------------------------

void
Semaphore::V()
{
    Thread *thread;
    IntStatus oldLevel = interrupt->SetLevel(IntOff);

    thread = (Thread *)queue->Remove();
    if (thread != NULL)	   // make thread ready, consuming the V immediately
	scheduler->ReadyToRun(thread);
    value++;
    (void) interrupt->SetLevel(oldLevel);
}

// Dummy functions -- so we can compile our later assignments 
// Note -- without a correct implementation of Condition::Wait(), 
// the test case in the network assignment won't work!
Lock::Lock(char* debugName) 
{
  //start semaphore with value of 1, I belive to let one in first
  threadInLock = NULL;
  name = debugName;
  value = 1;
  queue = new List;
}

Lock::~Lock() {}

void Lock::Acquire() 
{
   IntStatus oldLevel = interrupt->SetLevel(IntOff);	// disable interrupts
    
    while (value == 0) { 			// semaphore not available

#ifdef USER_PROGRAM
      //printf("AS %d: lock %s acquire failed, going to sleep\n", currentThread->space, name);
#endif
      
	queue->Append((void *)currentThread);	// so go to sleep
	currentThread->Sleep();

#ifdef USER_PROGRAM
	// printf("AS %d: awake from lock %s queue, trying to acquire lock again\n", currentThread->space, name);
#endif	
    } 
    
    //if not 0 then enter lock and save curent Thread in lock
    if(value != 0)
      {
	value--;
	threadInLock = currentThread;

#ifdef USER_PROGRAM
	//printf("AS %d: lock %s acquired!\n", currentThread->space, name);
#endif
      }

    (void) interrupt->SetLevel(oldLevel);	// re-enable interrupts
 
}

void Lock::Release() 
{

  //make only acquired thread can call release
  if(currentThread == threadInLock)
    {
      Thread *thread;

      IntStatus oldLevel = interrupt->SetLevel(IntOff);
     

      thread = (Thread *)queue->Remove();
      if (thread != NULL)
	{	   
	  scheduler->ReadyToRun(thread);
	}

      value++;
      threadInLock = NULL;

#ifdef USER_PROGRAM
      //printf("AS %d: lock %s acquire released!\n", currentThread->space, name);
#endif

      (void) interrupt->SetLevel(oldLevel);
    }
  
}

Condition::Condition(char* debugName) 
{ 
  name = debugName;
  lock = NULL;
  queue = new List();
}

Condition::~Condition() { }

void Condition::Wait(Lock* conditionLock) 
{ 
  // ASSERT(FALSE);not sure why this was here
  
  IntStatus oldLevel = interrupt->SetLevel(IntOff);//just as a precaution
  
  //save lock if first wait call
  if(lock == NULL)
    {
      lock = conditionLock;
    } 

  if(lock == conditionLock)
    {
      conditionLock->Release();
      queue->Append((void *)currentThread);//to signal or broadcast to it
      currentThread->Sleep();
 
      conditionLock->Acquire();
    }

  else if(lock != conditionLock)
    {
      printf("WARNING: Thread %s has wrong lock for condition\n",currentThread->getName());
    }

 (void) interrupt->SetLevel(oldLevel);
}

void Condition::Signal(Lock* conditionLock) 
{
   IntStatus oldLevel = interrupt->SetLevel(IntOff);//just as a precaution
   
   //to deal with Mesa race condition of broadcast
   if(!queue->IsEmpty())
     {

       if(conditionLock == lock)
	 {
	   Thread *thread = (Thread *)queue->Remove();
	   scheduler->ReadyToRun(thread);
	 }

       else
	 {
	   printf("WARNING:  Thread %s has wrong lock for condition\n",currentThread->getName());
	 }
     }

   else 
     {
       conditionLock->Release();
     }

   (void) interrupt->SetLevel(oldLevel);
}

void Condition::Broadcast(Lock* conditionLock) 
{
  //while wait queue is not empty
  while(!queue->IsEmpty())
    {
      Signal(conditionLock);
    }
}
